/runlibfile where { pop }{ /runlibfile { run } def } ifelse % 
(types.ps) runlibfile
(reader.ps) runlibfile
(printer.ps) runlibfile
(env.ps) runlibfile
(core.ps) runlibfile

% read
/_readline { print flush (%stdin) (r) file 1024 string readline } def

/READ {
    /str exch def
    str read_str
} def


% eval
/EVAL { 7 dict begin
    { %loop (TCO)

    /env exch def
    /ast exch def

    env (DEBUG-EVAL) env_get {
        dup null ne    exch false ne    and {
            (EVAL: ) print
            ast true _pr_str print
            (\n) print
        } if
    } if

    ast _symbol? { %if symbol
        env ast env_get
        {
            exit
        }{
            (') ast
            dup length string cvs
            (' not found)
            concatenate concatenate
            _throw
        } ifelse
    } if

    ast _vector? {
        [
            ast /data get { %forall items
                env EVAL
            } forall
        ] _vector_from_array
        exit
    } if

    ast _hash_map? {
        <<
            ast /data get { %forall entries
                env EVAL
            } forall
        >> _hash_map_from_dict
        exit
    } if

    ast _list? not {
        ast
        exit
    } if

    ast _count 0 eq {
        ast
        exit
    } if

        /a0 ast 0 _nth def

        /def! a0 eq { %if def!
            ast 2 _nth    env EVAL
            env    ast 1 _nth    2 index    env_set
            exit
        } if

        /let* a0 eq { %if let*
            /a1 ast 1 _nth def
            /a2 ast 2 _nth def
            /let_env env null null env_new def
            0 2 a1 _count 1 sub { %for each pair
                /idx exch def
                let_env
                    a1 idx _nth
                    a1 idx 1 add _nth let_env EVAL
                    env_set
            } for
            a2
            let_env
            % loop
        }{

        /do a0 eq { %if do
            ast _count 2 gt { %if ast has more than 2 elements
                ast 1 ast _count 2 sub _slice /data get { env EVAL pop } forall
            } if
            ast ast _count 1 sub _nth % last ast becomes new ast
            env
            % loop
        }{

        /if a0 eq { %if if
            /a1 ast 1 _nth def
            /cond a1 env EVAL def
            cond null eq cond false eq or { % if cond is nil or false
                ast _count 3 gt { %if false branch with a3
                    ast 3 _nth env
                    % loop
                }{ % else false branch with no a3
                    null
                    exit
                } ifelse
            }{ % true branch
                ast 2 _nth env
                % loop
            } ifelse
        }{

        /fn* a0 eq { %if fn*
            /a1 ast 1 _nth def
            /a2 ast 2 _nth def
            a2 env a1 _mal_function
            exit
        } if

            a0 env EVAL

            dup _mal_function? { %if user defined function
              [ ast _rest /data get { env EVAL } forall ] _list_from_array exch
              fload % stack: ast new_env
              % loop
            }{

            dup _function? { %else if builtin function
                [ ast _rest /data get { env EVAL } forall ] _list_from_array exch
                /data get exec
                exit
            } if

            %else (regular procedure/function)
            (cannot apply native proc!\n) print quit

            } ifelse } ifelse } ifelse } ifelse
    } loop % TCO
end } def


% print
/PRINT {
    true _pr_str
} def


% repl
/repl_env null null null env_new def

/RE { READ repl_env EVAL } def
/REP { READ repl_env EVAL PRINT } def

% core.ps: defined using postscript
core_ns { _function    repl_env 3 1 roll env_set } forall

% core.mal: defined using the language itself
(\(def! not \(fn* \(a\) \(if a false true\)\)\)) RE pop

% repl loop
{ %loop
    (user> ) _readline
    not { exit } if  % exit if EOF

    { %try
        REP print (\n) print
    } stopped {
        (Error: ) print
        get_error_data false _pr_str print (\n) print
        $error /newerror false put
        $error /errorinfo null put
        clear
        cleardictstack
    } if
} bind loop

(\n) print  % final newline before exit for cleanliness
quit
